// SPDX-License-Identifier: GPL-2.0-only
#include <linux/kernel.h>
#include <linux/kallsyms.h>
#include <linux/module.h>
#include <linux/percpu.h>
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/sizes.h>
#include <linux/stacktrace.h>
#include <linux/timer.h>
#include <linux/uaccess.h>
#include <linux/kprobes.h>
#include <linux/version.h>
#include "mod_detector.h"

static struct module_info g_module_list[] ____cacheline_aligned_in_smp = {
    { .name = "cn_vpps"},
    {}
};

static inline void print_kernel_module_info(struct module *mod)
{
    pr_debug("[%s.init]base=0x%lx, size=0x%x\n", mod->name, (unsigned long)mod->init_layout.base, mod->init_layout.size);
    pr_debug("[%s.core]base=0x%lx, size=0x%x\n", mod->name, (unsigned long)mod->core_layout.base, mod->core_layout.size);
}

static inline struct module_info *lookup_module_by_name(char *name)
{
    struct module_info *cnmod = g_module_list;

    /* find module exist */
    for (; cnmod->name[0]; cnmod++) {
        if (!strncmp(cnmod->name, name, MODULE_NAME_LEN))
            return cnmod;
    }

    return NULL;
}

/*
 * get the module whose code contains an address.
 */
struct module_info *lookup_module_by_text_address(unsigned long addr)
{
    struct module_info *cnmod = g_module_list;
    unsigned long flags;

    /* find module exist */
    for (; cnmod->name[0]; cnmod++) {
        raw_spin_lock_irqsave(&cnmod->lock, flags);
        if (cnmod->live && within_module(addr, cnmod->mod)) {
            raw_spin_unlock_irqrestore(&cnmod->lock, flags);
            return cnmod;
        }
        raw_spin_unlock_irqrestore(&cnmod->lock, flags);
    }

    return NULL;
}

static int mod_detector_notifier(struct notifier_block *nb, unsigned long event,
                                void *data)
{
    struct module *mod = data;
    struct module_info *cnmod;
    unsigned long flags;

    cnmod = lookup_module_by_name(mod->name);
    if (!cnmod)
        return NOTIFY_OK;

    switch (event) {
    case MODULE_STATE_COMING:
        /* our module is about to be running module_init */
        raw_spin_lock_irqsave(&cnmod->lock, flags);
        cnmod->mod = mod;
        cnmod->live = true;
        cnmod->module_size = mod->init_layout.size + mod->core_layout.size;
        raw_spin_unlock_irqrestore(&cnmod->lock, flags);

        pr_debug("[%s] insmod\n", cnmod->name);
        print_kernel_module_info(mod);
        break;
    case MODULE_STATE_GOING:
        /* rmmod */
        raw_spin_lock_irqsave(&cnmod->lock, flags);
        cnmod->mod = NULL;
        cnmod->live = false;
        cnmod->module_size = 0;
        raw_spin_unlock_irqrestore(&cnmod->lock, flags);

        pr_debug("[%s] rmmod\n", cnmod->name);
        print_kernel_module_info(mod);
        break;
    default:
        break;
    }

    return NOTIFY_OK;
}
static struct notifier_block mod_detector_nb = {
    .notifier_call = mod_detector_notifier,
};

int module_detector_init(void)
{
    struct module_info *cnmod = g_module_list;

    for (; cnmod->name[0]; cnmod++) {
        raw_spin_lock_init(&cnmod->lock);

        /* found exist module */
        cnmod->mod = find_module(cnmod->name);
        cnmod->live = cnmod->mod ? true : false;
        cnmod->module_size = cnmod->mod ?cnmod->mod->init_layout.size + cnmod->mod->core_layout.size : 0;
    }

    return register_module_notifier(&mod_detector_nb);
}

int module_detector_deinit(void)
{
    struct module_info *cnmod = g_module_list;

    /* clean module list */
    for (; cnmod->name[0]; cnmod++) {
        cnmod->live = true;
        cnmod->mod = NULL;
    }

    return unregister_module_notifier(&mod_detector_nb);
}

MODULE_LICENSE("GPL");
